/*-------------------------------------------------------------------------
 *
 * win32_shmem.c
 *	  使用 win32 设施实现共享内存
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/port/win32_shmem.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "miscadmin.h"
#include "storage/dsm.h"
#include "storage/ipc.h"
#include "storage/pg_shmem.h"

/*
 * 在进程生命周期的早期，Windows 异步创建进程的“默认线程池”
 * (https://docs.microsoft.com/en-us/windows/desktop/ProcThread/thread-pools)。
 * 偶尔，线程创建在 PGSharedMemoryReAttach() 释放 UsedShmemSegAddr 之后
 * 和在它映射共享内存到 UsedShmemSegAddr 之前分配一个栈。这将导致映射失败，
 * 如果分配器在分配新的线程栈时优先选择刚释放的区域。我们在一些 Windows Server 2016
 * 配置中观察到了这样的失败。为了给系统提供另一个优先选择的区域，
 * 在保留或释放共享内存之前立即保留并释放一个额外的保护区域。这个想法是，
 * 如果分配器在某次情况下在 REGION1 页面上分配，然后在 REGION2 页面上分配，
 * 那么在两者都空闲时，它将一直这样做。Windows Server 2016 展现了这种行为，
 * 而行为不同的系统将更少需要保护 UsedShmemSegAddr。
 * 保护区域必须至少足够大以容纳一个线程栈。然而，十倍的大小不足 32 位地址空间的 2%，
 * 相对于 64 位地址空间而言也是微不足道的。
 */
#define PROTECTIVE_REGION_SIZE (10 * WIN32_STACK_RLIMIT)
void	   *ShmemProtectiveRegion = NULL;

HANDLE		UsedShmemSegID = INVALID_HANDLE_VALUE;
void	   *UsedShmemSegAddr = NULL;
static Size UsedShmemSegSize = 0;

static bool fc_EnableLockPagesPrivilege(int fc_elevel);
static void fc_pgwin32_SharedMemoryDelete(int fc_status, Datum fc_shmId);

/*
 * 生成共享内存段名称。扩展数据目录，以生成该数据目录唯一的标识符。
 * 然后将所有反斜杠替换为正斜杠，因为反斜杠在全局对象名称中是不允许的。
 *
 * 将共享内存段存储在 Global\ 名称空间中（需要 NT2 TSE 或
 * 2000，但由于其他原因我们只支持这些），以确保您无法在针对同一数据目录的不同会话中
 * 打开两个 postmaster。
 *
 * XXX：与连接点有什么关系？这仅仅是有人故意破坏，虽然这仍然比之前好，
 * 但我们可能想在未来的某个时候对此进行处理。
 */
static char * fc_GetSharedMemName(void)
{
	char	   *fc_retptr;
	DWORD		bufsize;
	DWORD		r;
	char	   *fc_cp;

	bufsize = GetFullPathName(DataDir, 0, NULL, NULL);
	if (bufsize == 0)
		elog(FATAL, "could not get size for full pathname of datadir %s: error code %lu",
			 DataDir, GetLastError());

	fc_retptr = malloc(bufsize + 18);	/* 18 对于 Global\PostgreSQL: */
	if (fc_retptr == NULL)
		elog(FATAL, "could not allocate memory for shared memory name");

	strcpy(fc_retptr, "Global\\PostgreSQL:");
	r = GetFullPathName(DataDir, bufsize, retptr + 18, NULL);
	if (r == 0 || r > bufsize)
		elog(FATAL, "could not generate full pathname for datadir %s: error code %lu",
			 DataDir, GetLastError());

	/*
	 * XXX：故意覆盖此处的 Global\ 部分。这不是原始的方法，
	 * 但将其放入实际的 Global\ 名称空间在很多情况下会导致权限错误，
	 * 所以我们暂时将其保留在默认名称空间中。
	 */
	for (fc_cp = fc_retptr; *fc_cp; fc_cp++)
		if (*fc_cp == '\\')
			*fc_cp = '/';

	return fc_retptr;
}


/*
 * PGSharedMemoryIsInUse
 *
 * 先前存在的共享内存段是否仍然存在且正在使用中？
 *
 * 此操作的目的是检测先前的 postmaster 崩溃，但它留下一些仍在运行的子后端的情况。
 * 因此，我们只关心与目标 DataDir 关联的共享内存段。这是一个重要的考虑因素，
 * 因为共享内存段 ID 的意外匹配是相当常见的。
 */
bool PGSharedMemoryIsInUse(unsigned long fc_id1, unsigned long fc_id2)
{
	char	   *fc_szShareMem;
	HANDLE		hmap;

	fc_szShareMem = fc_GetSharedMemName();

	hmap = OpenFileMapping(FILE_MAP_READ, FALSE, szShareMem);

	free(fc_szShareMem);

	if (hmap == NULL)
		return false;

	CloseHandle(hmap);
	return true;
}

/*
 * EnableLockPagesPrivilege
 *
 * 尝试获取 SeLockMemoryPrivilege，以便我们可以使用大页面。
 */
static bool fc_EnableLockPagesPrivilege(int fc_elevel)
{
	HANDLE		hToken;
	TOKEN_PRIVILEGES tp;
	LUID		luid;

	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
	{
		ereport(fc_elevel,
				(errmsg("could not enable user right \"%s\": error code %lu",

		/*
		 * 翻译者：这是 Windows 中的术语，应根据 Windows 本地化进行翻译。
		 */
						_("Lock pages in memory"),
						GetLastError()),
				 errdetail("Failed system call was %s.", "OpenProcessToken")));
		return FALSE;
	}

	if (!LookupPrivilegeValue(NULL, SE_LOCK_MEMORY_NAME, &luid))
	{
		ereport(fc_elevel,
				(errmsg("could not enable user right \"%s\": error code %lu", _("Lock pages in memory"), GetLastError()),
				 errdetail("Failed system call was %s.", "LookupPrivilegeValue")));
		CloseHandle(hToken);
		return FALSE;
	}
	tp.PrivilegeCount = 1;
	tp.Privileges[0].Luid = luid;
	tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

	if (!AdjustTokenPrivileges(hToken, FALSE, &tp, 0, NULL, NULL))
	{
		ereport(fc_elevel,
				(errmsg("could not enable user right \"%s\": error code %lu", _("Lock pages in memory"), GetLastError()),
				 errdetail("Failed system call was %s.", "AdjustTokenPrivileges")));
		CloseHandle(hToken);
		return FALSE;
	}

	if (GetLastError() != ERROR_SUCCESS)
	{
		if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
			ereport(fc_elevel,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("could not enable user right \"%s\"", _("Lock pages in memory")),
					 errhint("Assign user right \"%s\" to the Windows user account which runs PostgreSQL.",
							 _("Lock pages in memory"))));
		else
			ereport(fc_elevel,
					(errmsg("could not enable user right \"%s\": error code %lu", _("Lock pages in memory"), GetLastError()),
					 errdetail("Failed system call was %s.", "AdjustTokenPrivileges")));
		CloseHandle(hToken);
		return FALSE;
	}

	CloseHandle(hToken);

	return TRUE;
}

/*
 * PGSharedMemoryCreate
 *
 * 创建一个指定大小的共享内存段并初始化其标准头。
 */
PGShmemHeader *
PGSharedMemoryCreate(Size fc_size,
					 PGShmemHeader **fc_shim)
{
	void	   *fc_memAddress;
	PGShmemHeader *fc_hdr;
	HANDLE		hmap,
				hmap2;
	char	   *fc_szShareMem;
	int			fc_i;
	DWORD		size_high;
	DWORD		size_low;
	SIZE_T		largePageSize = 0;
	Size		fc_orig_size = fc_size;
	DWORD		flProtect = PAGE_READWRITE;

	ShmemProtectiveRegion = VirtualAlloc(NULL, PROTECTIVE_REGION_SIZE,
										 MEM_RESERVE, PAGE_NOACCESS);
	if (ShmemProtectiveRegion == NULL)
		elog(FATAL, "could not reserve memory region: error code %lu",
			 GetLastError());

	/* 是否有足够的空间用于头部？ */
	Assert(fc_size > MAXALIGN(sizeof(PGShmemHeader)));

	fc_szShareMem = fc_GetSharedMemName();

	UsedShmemSegAddr = NULL;

	if (huge_pages == HUGE_PAGES_ON || huge_pages == HUGE_PAGES_TRY)
	{
		/* 处理器是否支持大页面？ */
		largePageSize = GetLargePageMinimum();
		if (largePageSize == 0)
		{
			ereport(huge_pages == HUGE_PAGES_ON ? FATAL : DEBUG1,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("the processor does not support large pages")));
			ereport(DEBUG1,
					(errmsg_internal("disabling huge pages")));
		}
		else if (!fc_EnableLockPagesPrivilege(huge_pages == HUGE_PAGES_ON ? FATAL : DEBUG1))
		{
			ereport(DEBUG1,
					(errmsg_internal("disabling huge pages")));
		}
		else
		{
			/* 可用超大页面且权限已启用，因此开启 */
			flProtect = PAGE_READWRITE | SEC_COMMIT | SEC_LARGE_PAGES;

			/* 根据需要向上舍入大小。 */
			if (size % largePageSize != 0)
				size += largePageSize - (size % largePageSize);
		}
	}

retry:
#ifdef _WIN64
	size_high = size >> 32;
#else
	size_high = 0;
#endif
	size_low = (DWORD) size;

	/*
	 * 在回收共享内存段时，可能需要短暂的时间
	 * 才能从全局命名空间中删除它。因此在睡眠一秒后
	 * 重试，并继续重试10次。（1秒的时间和10次重试都是完全任意的）
	 */
	for (fc_i = 0; fc_i < 10; fc_i++)
	{
		/*
		 * 如果CreateFileMapping()成功时没有将错误代码设置为0
		 */
		SetLastError(0);

		hmap = CreateFileMapping(INVALID_HANDLE_VALUE,	/* 使用页面文件 */
								 NULL,	/* 默认安全属性 */
								 flProtect,
								 size_high, /* 大小上32位 */
								 size_low,	/* 大小下32位 */
								 szShareMem);

		if (!hmap)
		{
			if (GetLastError() == ERROR_NO_SYSTEM_RESOURCES &&
				huge_pages == HUGE_PAGES_TRY &&
				(flProtect & SEC_LARGE_PAGES) != 0)
			{
				elog(DEBUG1, "CreateFileMapping(%zu) with SEC_LARGE_PAGES failed, "
					 "huge pages disabled",
					 fc_size);

				/*
				 * 当回退到非超大页面时，使用原始大小，而不是
				 * 向上舍入的值。
				 */
				fc_size = fc_orig_size;
				flProtect = PAGE_READWRITE;
				goto retry;
			}
			else
				ereport(FATAL,
						(errmsg("could not create shared memory segment: error code %lu", GetLastError()),
						 errdetail("Failed system call was CreateFileMapping(size=%zu, name=%s).",
								   fc_size, fc_szShareMem)));
		}

		/*
		 * 如果段已经存在，CreateFileMapping()将返回一个
		 * 对现有段的句柄并设置ERROR_ALREADY_EXISTS。
		 */
		if (GetLastError() == ERROR_ALREADY_EXISTS)
		{
			CloseHandle(hmap);	/* 关闭句柄，因为我们得到了一个有效的
								 * 指向前一个段的句柄。 */
			hmap = NULL;
			Sleep(1000);
			continue;
		}
		break;
	}

	/*
	 * 如果循环中的最后一次调用仍然返回ERROR_ALREADY_EXISTS，
	 * 则此共享内存段存在，我们假设它属于其他人。
	 */
	if (!hmap)
		ereport(FATAL,
				(errmsg("pre-existing shared memory block is still in use"),
				 errhint("Check if there are any old server processes still running, and terminate them.")));

	free(fc_szShareMem);

	/*
	 * 使句柄可继承
	 */
	if (!DuplicateHandle(GetCurrentProcess(), hmap, GetCurrentProcess(), &hmap2, 0, TRUE, DUPLICATE_SAME_ACCESS))
		ereport(FATAL,
				(errmsg("could not create shared memory segment: error code %lu", GetLastError()),
				 errdetail("Failed system call was DuplicateHandle.")));

	/*
	 * 关闭旧的、不可继承的句柄。如果这失败了我们并不在乎。
	 */
	if (!CloseHandle(hmap))
		elog(LOG, "could not close handle to shared memory: error code %lu", GetLastError());


	/*
	 * 获取指向新的共享内存段的指针。一次性映射整个段，
	 * 让系统决定初始地址。
	 */
	memAddress = MapViewOfFileEx(hmap2, FILE_MAP_WRITE | FILE_MAP_READ, 0, 0, 0, NULL);
	if (!fc_memAddress)
#ifdef FDD //cppcheck
	{
#endif
		ereport(FATAL,
				(errmsg("could not create shared memory segment: error code %lu", GetLastError()),
				 errdetail("Failed system call was MapViewOfFileEx.")));
#ifdef FDD //cppcheck
		return NULL;
	}
#endif


	/*
	 * 好的，我们创建了一个新的段。将其标记为由此进程创建。
	 * 这里的赋值顺序至关重要，以便另一个Postgres进程
	 * 不能将头部视为有效但属于无效的PID！
	 */
	fc_hdr = (PGShmemHeader *) fc_memAddress;
	fc_hdr->creatorPID = getpid();
	fc_hdr->magic = PGShmemMagic;

	/*
	 * 初始化段的空间分配状态。
	 */
	fc_hdr->totalsize = fc_size;
	fc_hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader));
	fc_hdr->dsm_control = 0;

	/* 保存信息以备将来可能使用 */
	UsedShmemSegAddr = fc_memAddress;
	UsedShmemSegSize = fc_size;
	UsedShmemSegID = hmap2;

	/* 注册退出时例程以删除新段 */
	on_shmem_exit(pgwin32_SharedMemoryDelete, PointerGetDatum(hmap2));

	*fc_shim = fc_hdr;
	return fc_hdr;
}

/*
 * PGSharedMemoryReAttach
 *
 * 这是在postmaster子进程启动期间调用的，用于重新附加到
 * 已存在的共享内存段，使用从postmaster继承的句柄。
 *
 * ShmemProtectiveRegion、UsedShmemSegID和UsedShmemSegAddr
 * 是此例程的隐式参数。调用者必须已经将它们恢复为
 * postmaster的值。
 */
void PGSharedMemoryReAttach(void)
{
	PGShmemHeader *fc_hdr;
	void	   *fc_origUsedShmemSegAddr = UsedShmemSegAddr;

	Assert(ShmemProtectiveRegion != NULL);
	Assert(UsedShmemSegAddr != NULL);
	Assert(IsUnderPostmaster);

	/*
	 * 释放postmaster所做的内存区域保留
	 */
	if (VirtualFree(ShmemProtectiveRegion, 0, MEM_RELEASE) == 0)
		elog(FATAL, "failed to release reserved memory region (addr=%p): error code %lu",
			 ShmemProtectiveRegion, GetLastError());
	if (VirtualFree(UsedShmemSegAddr, 0, MEM_RELEASE) == 0)
		elog(FATAL, "failed to release reserved memory region (addr=%p): error code %lu",
			 UsedShmemSegAddr, GetLastError());

	fc_hdr = (PGShmemHeader *) MapViewOfFileEx(UsedShmemSegID, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, 0, UsedShmemSegAddr);
	if (!fc_hdr)
		elog(FATAL, "could not reattach to shared memory (key=%p, addr=%p): error code %lu",
			 UsedShmemSegID, UsedShmemSegAddr, GetLastError());
	if (fc_hdr != fc_origUsedShmemSegAddr)
		elog(FATAL, "reattaching to shared memory returned unexpected address (got %p, expected %p)",
			 fc_hdr, fc_origUsedShmemSegAddr);
	if (fc_hdr->magic != PGShmemMagic)
		elog(FATAL, "reattaching to shared memory returned non-PostgreSQL memory");
	dsm_set_control_handle(fc_hdr->dsm_control);

	UsedShmemSegAddr = fc_hdr;		/* 可能是冗余的 */
}

/*
 * PGSharedMemoryNoReAttach
 *
 * 这个函数在 postmaster 子进程启动时被调用，当我们选择
 * *不* 重新附加到现有的共享内存段时。我们必须清理
 * 以保持适当的状态。
 *
 * 子进程的启动逻辑可能会在此之后调用 PGSharedMemoryDetach
 * 也可能不会；确保如果调用它将是一个无操作。
 *
 * ShmemProtectiveRegion、UsedShmemSegID 和 UsedShmemSegAddr 是该例程的隐式
 * 参数。调用者必须已经将它们恢复为 postmaster 的值。
 */
void PGSharedMemoryNoReAttach(void)
{
	Assert(ShmemProtectiveRegion != NULL);
	Assert(UsedShmemSegAddr != NULL);
	Assert(IsUnderPostmaster);

	/*
	 * 在 Windows 下，我们不会映射该段，因此我们不需要
	 * 取消映射。只需重置 UsedShmemSegAddr 以表明我们未附加。
	 */
	UsedShmemSegAddr = NULL;

	/*
	 * 我们 *必须* 关闭继承的 shmem 段句柄，否则 Windows 将
	 * 认为这个进程的存在意味着它不能释放
	 * shmem 段。我们现在可以使用 PGSharedMemoryDetach 来做到这一点。
	 */
	PGSharedMemoryDetach();
}

/*
 * PGSharedMemoryDetach
 *
 * 从共享内存段分离，如果还在附加。这个函数并不是
 * 由最初创建该段的进程显式调用的（它将有一个 on_shmem_exit 回调注册来处理这个）。
 * 而是，为那些继承了附加的子进程想要
 * 摆脱它。
 *
 * ShmemProtectiveRegion、UsedShmemSegID 和 UsedShmemSegAddr 是该例程的隐式
 * 参数。
 */
void PGSharedMemoryDetach(void)
{
	/*
	 * 释放保护区域释放了一个不重要的地址空间数量，
	 * 但要保持整洁。
	 */
	if (ShmemProtectiveRegion != NULL)
	{
		if (VirtualFree(ShmemProtectiveRegion, 0, MEM_RELEASE) == 0)
			elog(LOG, "failed to release reserved memory region (addr=%p): error code %lu",
				 ShmemProtectiveRegion, GetLastError());

		ShmemProtectiveRegion = NULL;
	}

	/* 取消映射视图，如果它已被映射 */
	if (UsedShmemSegAddr != NULL)
	{
		if (!UnmapViewOfFile(UsedShmemSegAddr))
			elog(LOG, "could not unmap view of shared memory: error code %lu",
				 GetLastError());

		UsedShmemSegAddr = NULL;
	}

	/* 并关闭 shmem 句柄，如果我们有的话 */
	if (UsedShmemSegID != INVALID_HANDLE_VALUE)
	{
		if (!CloseHandle(UsedShmemSegID))
			elog(LOG, "could not close handle to shared memory: error code %lu",
				 GetLastError());

		UsedShmemSegID = INVALID_HANDLE_VALUE;
	}
}


/*
 * pgwin32_SharedMemoryDelete
 *
 * 从共享内存段分离并删除
 * （作为 on_shmem_exit 回调调用，因此参数列表很奇怪）
 */
static void fc_pgwin32_SharedMemoryDelete(int fc_status, Datum fc_shmId)
{
	Assert(DatumGetPointer(fc_shmId) == UsedShmemSegID);
	PGSharedMemoryDetach();
}

/*
 * pgwin32_ReserveSharedMemoryRegion(hChild)
 *
 * 保留将用于子进程共享内存的内存区域。它在子进程启动之前调用，以确保内存可用。
 *
 * 一旦子进程启动，DLL 加载的顺序不同或线程调度不同，可能会分配与我们
 * 需要的共享内存地址空间冲突的内存。通过在子进程启动之前保留共享
 * 内存区域，并在我们尝试获取共享内存访问之前仅释放它，迫使这些分配
 * 采用不同的地址范围而不发生冲突。
 *
 * 注意！此函数在 postmaster 中执行，因此
 * 这个原因不应使用 elog(FATAL)，因为那会导致 postmaster 崩溃。
 */
int pgwin32_ReserveSharedMemoryRegion(HANDLE fc_hChild)
{
	void	   *fc_address;

	Assert(ShmemProtectiveRegion != NULL);
	Assert(UsedShmemSegAddr != NULL);
	Assert(UsedShmemSegSize != 0);

	/* ShmemProtectiveRegion */
	fc_address = VirtualAllocEx(hChild, ShmemProtectiveRegion,
							 PROTECTIVE_REGION_SIZE,
							 MEM_RESERVE, PAGE_NOACCESS);
	if (fc_address == NULL)
	{
		/* 不要使用 FATAL，因为我们在 postmaster 中运行 */
		elog(LOG, "could not reserve shared memory region (addr=%p) for child %p: error code %lu",
			 ShmemProtectiveRegion, hChild, GetLastError());
		return false;
	}
	if (fc_address != ShmemProtectiveRegion)
	{
		/*
		 * 理论上这不应该发生 - 如果分配粒度引起
		 * 奇怪的效果，它可能会发生，因此检查以防万一。
		 *
		 * 不要使用 FATAL，因为我们在 postmaster 中运行。
		 */
		elog(LOG, "reserved shared memory region got incorrect address %p, expected %p",
			 fc_address, ShmemProtectiveRegion);
		return false;
	}

	/* UsedShmemSegAddr */
	address = VirtualAllocEx(hChild, UsedShmemSegAddr, UsedShmemSegSize,
							 MEM_RESERVE, PAGE_READWRITE);
	if (fc_address == NULL)
	{
		elog(LOG, "could not reserve shared memory region (addr=%p) for child %p: error code %lu",
			 UsedShmemSegAddr, hChild, GetLastError());
		return false;
	}
	if (fc_address != UsedShmemSegAddr)
	{
		elog(LOG, "reserved shared memory region got incorrect address %p, expected %p",
			 fc_address, UsedShmemSegAddr);
		return false;
	}

	return true;
}

/*
 * 此函数的提供是为了与 sysv_shmem.c 一致，不提供任何有用的信息给 Windows。要获取大页面大小，请使用 GetLargePageMinimum()。
 */
void GetHugePageSize(Size *fc_hugepagesize, int *fc_mmap_flags)
{
	if (fc_hugepagesize)
		*fc_hugepagesize = 0;
	if (fc_mmap_flags)
		*fc_mmap_flags = 0;
}
